package com.joinway.extension.socket.server.codec;

import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.CumulativeProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.joinway.extension.socket.bean.InputFrameSegmentConfig;
import com.joinway.extension.socket.serializer.StringSerializer;
import com.joinway.extension.socket.serializer.ISegmentSerializer;

public abstract class AbstractFrameDataDecoder<T> extends CumulativeProtocolDecoder {

	private final static Logger log = LoggerFactory.getLogger(AbstractFrameDataDecoder.class);
	
	protected abstract List<InputFrameSegmentConfig> getInputFrameSegmentConfig();
	
	protected abstract int getTotalByteCount();
	
	protected abstract Class<T> getTarget();
	
	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Override
	protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
		boolean eof = false;
		
		int remains = in.remaining();
		int total = getTotalByteCount();
		log.debug("remaining bytes is {}, expected bytes {}", remains, total);
		
		if(remains >= total){
			int sum = 0;
			T target = getTarget().newInstance();
			
			List<InputFrameSegmentConfig> configs = getInputFrameSegmentConfig();
			
			byte[] input = new byte[total];
			in.get(input);
			
 			for(InputFrameSegmentConfig config : configs){
				int count = config.getByteCount();
				byte[] data = new byte[count];
				
				for(int i = 0; i < count; i++){
					int index = i + sum;
					data[i] = input[index];
				}
				
				ISegmentSerializer converter = config.getSerializer() != null ? config.getSerializer() : getDefaultSerializer();
				Object value = converter.deserialize(data);
//				log.debug("converted value is {}", value);
				
				if(value != null){
					if(config.getValidator() != null && !config.getValidator().validate(value)){
						throw new Exception("Invalid frame value " + value);
					}else{
						setFieldValue(target, config.getField(), value);
					}
				}
				
				sum += count;
			}
			
			out.write(target);
			
			eof = true;
		}
		
		return eof;
	}
	
	protected void setFieldValue(Object target, String fieldName, Object value) throws Exception{
		String methodName = "set" + StringUtils.capitalize(fieldName);
		MethodUtils.invokeMethod(target, methodName, value);
	}
	
	@SuppressWarnings("rawtypes")
	protected ISegmentSerializer getDefaultSerializer(){
		return StringSerializer.Instance;
	}
}
